package com.kornero.auto.controller;

import com.kornero.auto.entities.Car;
import com.kornero.auto.net.parsers.IParser;
import com.kornero.auto.net.parsers.ParserFactory;
import com.kornero.auto.net.parsers.ParserNotFound;
import com.kornero.auto.util.Sounder;
import com.kornero.auto.util.Threads;
import com.kornero.auto.util.data.DataSource;
import com.kornero.auto.util.email.EmailUtils;
import com.kornero.auto.view.MainForm;
import com.kornero.auto.view.groups.IMainGroup;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class Controller {

    private static final Logger logger = LoggerFactory.getLogger(Controller.class);
    private static final int TIMEOUT = 1500;

    private final Set<Car> cars = Collections.newSetFromMap(new ConcurrentHashMap<Car, Boolean>());
    private final List<String> urls;

    private final AutoUpdateController autoUpdate;
    private final MainGroupController mainGroup;
    private final EmailController emailGroup;

    private final Thread gui;

    private MainForm mainForm;

    public Controller() {
        this.cars.addAll(DataSource.loadCars());
        this.urls = DataSource.loadUrls();
        this.gui = new Thread(new Runnable() {
            @Override
            public void run() {
                final MainForm mainForm = new MainForm();
                setMainForm(mainForm);
                mainForm.show();
                stop();
            }
        });
        gui.start();

        while (null == mainForm) {
            Threads.sleep(100);
        }

        autoUpdate = new AutoUpdateController(new RequestTask(), mainForm.getPropertiesPanel().getAutoUpdate());
        mainGroup = new MainGroupController(new RequestTask(), mainForm.getPropertiesPanel().getMain());
        emailGroup = new EmailController(mainForm.getPropertiesPanel().getEmail());

        this.bind();
        this.setUp();
        this.reloadTable();
    }

    public void stop() {
        autoUpdate.stop();
        mainGroup.stop();
        this.gui.interrupt();
        ParserFactory.closeAllParsers();
        DataSource.saveCars(cars);
    }

    private void setUp() {
        this.mainGroup.getView().setUpProgressBar(urls.size());
    }

    private void reloadTable() {
        this.mainForm.getTablePanel().setTable(this.cars);
        printCarsAmountMessage();
    }

    private void bind() {
        this.mainGroup.getView().addClearDBListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                clearAll();
            }
        });
        this.mainGroup.getView().addClearOldListener(new Listener() {
            @Override
            public void handleEvent(Event event) {
                clearOld();
            }
        });
        this.emailGroup.getView().addMarkAll(new Listener() {
            @Override
            public void handleEvent(Event event) {
                markAllAsRead();
            }
        });
    }

    private void markAllAsRead() {
        for (final Car car : cars) {
            car.setChecked(true);
        }
        reloadTable();
        DataSource.saveCars(cars);
    }

    private void clearAll() {
        this.cars.clear();
        this.mainForm.getTablePanel().removeAll();

        DataSource.saveCars(cars);
        this.printCarsAmountMessage();
    }

    private void clearOld() {
        final int daysOld = mainGroup.getView().getDaysOld();
        final long dateInMs = daysOld * 24L * 60L * 60L * 1000L;
        logger.debug("clearOld(): days old = {}, in ms = {}", daysOld, dateInMs);

        final Date date = new Date(System.currentTimeMillis() - dateInMs);
        logger.debug("clearOld(): Comparing with date: {}", date);

        final Set<Car> oldCars = new HashSet<>();
        for (final Car car : this.cars) {
            if (car.getDate().before(date)) {
                oldCars.add(car);
            }
        }

        // Remove old cars.
        if (!oldCars.isEmpty()) {
            this.cars.removeAll(oldCars);
            this.reloadTable();
        }

        DataSource.saveCars(cars);
        this.printCarsAmountMessage();
    }

    private void setMainForm(final MainForm mainForm) {
        this.mainForm = mainForm;
    }

    private class RequestTask implements Runnable {

        @Override
        public void run() {
            final IMainGroup mainGroupView = mainGroup.getView();
            mainGroupView.setUpProgressBar(urls.size());
            mainGroupView.resetProgressBar();
            final Thread me = Thread.currentThread();
            int i = 1;
            for (final String url : urls) {
                if (me.isInterrupted()) {
                    break;
                }
                mainGroupView.setConnectionStatus("[" + i++ + "/" + urls.size() + "] Parsing url: " + url);
                mainGroupView.incProgressBar();
                Threads.sleep(TIMEOUT);

                final IParser parser;
                try {
                    parser = ParserFactory.getParserForUrl(url);
                } catch (ParserNotFound parserNotFound) {
                    logger.error("run(): ", parserNotFound);
                    continue;
                }

                final Collection<Car> carList = parser.parsePage(url);
                logger.debug("Cars = {}", carList.size());

                // Skip if all cars was already found.
                if (cars.containsAll(carList)) {
                    continue;
                }

                // Add to the table new cars.
                this.processNewCars(carList);
            }
            mainGroupView.setConnectionStatus("saving cars.");
            DataSource.saveCars(cars);
            mainGroupView.setConnectionStatus("done.");
        }

        private void processNewCars(final Collection<Car> carCollection) {
            final boolean isSendEmails = emailGroup.getView().isSendEmails();
            final String emailAddress = emailGroup.getView().getEmail();

            for (final Car car : carCollection) {

                // Skip already found car.
                if (cars.contains(car)) {
                    continue;
                }
                if (cars.add(car)) {
                    mainForm.getTablePanel().addCar(car);
                    Sounder.notification();
                    if (isSendEmails) {
                        EmailUtils.sendCarOnEmail(car, emailAddress);
                    }
                } else {
                    logger.error("processNewCars(): DUPLICATE CAR = " + car);
                }
            }
            printCarsAmountMessage();
        }
    }

    private void printCarsAmountMessage() {
        final IMainGroup mainGroupView = mainGroup.getView();
        int newCars = 0;
        for (final Car car : this.cars) {
            if (!car.isChecked()) {
                newCars++;
            }
        }
        mainGroupView.setMessage(newCars + "/" + this.cars.size() + " messages.");
    }
}
